#include "goban.hpp"

#include <QDebug>

Goban::Goban(quint8 size, QObject * parent) :
             QObject(parent),
             _size(size),
             _map(size, QVector<quint8>(size, 0))
{
}

quint8 Goban::size() const
{
    return _size;
}

void Goban::setMap(const QVector < QVector<quint8> > & map)
{
    _map = map;
}

QVector< QVector<quint8> > Goban::map() const
{
    return _map;
}

bool Goban::isInRow(int x, int y) const
{
    return x >= 0 and y >= 0 and x < _size and y < _size;
}

bool Goban::canExtend(QPair<int, int> pawn) const
{
    bool canExtend = false;

    int x = pawn.first;
    int y = pawn.second;

    if(!isInRow(x, y))
        return false;

    if(x > 0)
       canExtend = _map[x-1][y] == 0;
    if(x < _size - 1  and !canExtend)
        canExtend = _map[x+1][y] == 0;
    if(y > 0 and !canExtend)
        canExtend = _map[x][y-1] == 0;
    if(y < _size - 1 and !canExtend)
        canExtend = _map[x][y+1] == 0;

    return canExtend;
}

bool Goban::isAlive(QVector <QPair<int, int> > group) const
{
    if (group.isEmpty())
        return true;

    for(int i = 0 ; i < group.size(); ++i)
        if (canExtend(group[i]))
            return true;

    return false;
}

void Goban::group_rec(int x, int y, QVector <QPair<int, int> > & group, quint8 color)
{
    if(isInRow(x, y))
    {
        if(_map[x][y] == color)
        {
            _map[x][y] = 42;

            QPair<int, int> pair(x, y);

            if (!group.contains(pair))
            {
                group.append(pair);

                group_rec(x-1, y, group, color);
                group_rec(x+1, y, group, color);
                group_rec(x, y-1, group, color);
                group_rec(x, y+1, group, color);
            }
        }
    }
}

void Goban::cleanMarkers(quint8 color)
{
    for(int i = 0 ; i < _map.size() ; ++i)
        for(int j = 0 ; j < _map[i].size() ; ++j)
            if(_map[i][j] == 42)
                _map[i][j] = color;
}

void Goban::score(qreal & black, qreal & white) const
{
    for (int x = 0; x < _size; ++x)
    {
        for (int y = 0; y < _size; ++y)
        {
            if (_map[x][y] == 4)
                black += 2;
            else if (_map[x][y] == 5)
                white += 2;
            else if (_map[x][y] == 6)
                ++white;
            else if (_map[x][y] == 7)
                ++black;
        }
    }
}

void Goban::deleteGroup(QVector <QPair< int, int> > group)
{
    for(int i = 0 ; i < group.size() ; ++i)
        _map[group[i].first][group[i].second] = 0;
}

QVector <QPair <int, int> > Goban::findGroup(int x, int y, bool color)
{
    QVector <QPair <int, int> > group;

    group_rec(x, y, group, (quint8)(color) + 1);
    cleanMarkers((quint8)(color) + 1);

    return group;
}

void Goban::debuging()
{
    for(int i = 0 ; i < _size ; ++i)
    {
        QString line;
        for(int j = 0 ; j < _size ; ++j)
        {
            if(_map[j][i] == 0)
                line += " ";
            else
                line += QString::number(_map[j][i]);
        }
    }
}

int Goban::playAMove(bool color, int x, int y)
{
    Goban gobanOld(_size, this);
    gobanOld._map = _map;

    int yEaten = 0, xEaten = 0;
    int eaten = 0;

    int nbStonesCaught = 0;

    if(!isInRow(x, y))
        return -42;

    if (_map[x][y] != 0)
        return -42;

    _map[x][y] = (quint8)(color) + 1;

    if(!isAlive(findGroup(x-1, y, !color)))
    {
        QVector <QPair <int, int> > group = findGroup(x-1, y, !color);
        if(group.size() == 1)
        {
            ++eaten;
            xEaten = x-1;
            yEaten = y;
        }
        nbStonesCaught += group.size();

        deleteGroup(group);
    }

    if(!isAlive(findGroup(x+1, y, !color)))
    {
        QVector <QPair <int, int> > group = findGroup(x+1, y, !color);
        if(group.size() == 1)
        {
            ++eaten;
            xEaten = x+1;
            yEaten = y;
        }

        nbStonesCaught += group.size();

        deleteGroup(group);
    }

    if(!isAlive(findGroup(x, y-1, !color)))
    {
        QVector <QPair <int, int> > group = findGroup(x, y-1, !color);
        if(group.size() == 1)
        {
            ++eaten;
            xEaten = x;
            yEaten = y-1;
        }

        nbStonesCaught += group.size();

        deleteGroup(group);
    }

    if(!isAlive(findGroup(x, y+1, !color)))
    {
        QVector <QPair <int, int> > group = findGroup(x, y+1, !color);
        if(group.size() == 1)
        {
            ++eaten;
            xEaten = x;
            yEaten = y+1;
        }

        nbStonesCaught += group.size();

        deleteGroup(group);
    }

    if(!isAlive(findGroup(x, y, color)))
    {
        _map[x][y] = 0;
        return -42;
    }

    if (!_isACopy)
    {
        if(eaten == 1)
        {
            Goban gobanNew(_size, this);
            gobanNew._map = _map;
            gobanNew._isACopy = true;

            if(checkKO(gobanNew, gobanOld, !color, xEaten, yEaten))
            {
                _map[xEaten][yEaten] = 3;

                if (_squareKO.first != 42)
                    _map[_squareKO.first][_squareKO.second] = 0;
                _squareKO = QPair<int, int>(xEaten, yEaten);
            }
            else if (_squareKO.first != 42)
            {
                _map[_squareKO.first][_squareKO.second] = 0;
                _squareKO = QPair<int, int> (42, 42);
            }
        }
        else if (_squareKO.first != 42)
        {
            _map[_squareKO.first][_squareKO.second] = 0;
            _squareKO = QPair<int, int> (42, 42);
        }
    }

    return nbStonesCaught;
}

void Goban::flood(QVector< QPair<int, int> > & map, bool color, int x, int y)
{
    if(isInRow(x, y))
    {
        if(_map[x][y] == 0 or (color and _map[x][y] == 9) or (!color && _map[x][y] == 8))
        {
            if (_map[x][y] == 0)
            {
                if(color)
                    _map[x][y] = 6;
                else
                    _map[x][y] = 7;
            }

            if (!map.contains(QPair<int, int> (x, y)))
            {
                map.append(QPair<int, int> (x, y));

                flood(map, color, x-1, y);
                flood(map, color, x+1, y);
                flood(map, color, x, y+1);
                flood(map, color, x, y-1);
            }
        }
    }
}

QVector <QPair<int, int> > Goban::floodEmptyTerritory(bool color, int x, int y)
{
    QVector < QPair <int, int> > map;

    flood(map, color, x, y);

    return map;
}

bool Goban::isATerritory(bool color, int x, int y)
{
    bool north = true,
         east = true,
         south = true,
         west = true;

    if(x-1 >= 0)
    {
        west = !(_map[x-1][y] == (quint8)(color) + 1);
    }

    if(x+1 < _size)
    {
        east = !(_map[x+1][y] == (quint8)(color) + 1);
    }

    if(y-1 >= 0)
    {
         north = !(_map[x][y-1] == (quint8)(color) + 1);
    }

    if(y+1 < _size)
    {
        south = !(_map[x][y+1] == (quint8)(color) + 1);
    }
    return north && east && west && south;
}

void Goban::unmark(const QVector <QPair <int, int> > & group, quint8 marker)
{
    for(int i = 0 ; i < group.size() ; ++i)
    {
        if(_map[group.at(i).first][group.at(i).second] == marker)
            _map[group.at(i).first][group.at(i).second] = 0;
    }
}

void Goban::remark(const QVector <QPair <int, int> > & group, quint8 marker, quint8 newMarker)
{
    for(int i = 0 ; i < group.size() ; ++i)
    {
        if(_map[group.at(i).first][group.at(i).second] == marker)
            _map[group.at(i).first][group.at(i).second] = newMarker;
    }
}

void Goban::display()
{
    for (int y = 0; y < _size; ++y)
    {
        QString line;

        for (int x = 0; x < _size; ++x)
        {
            if (_map[x][y] == 0)
                line.append(' ');
            else
                line.append((char)_map[x][y] + '0');
        }
    }
}

void Goban::declareDead(int x, int y)
{
    bool color;

    if (!isInRow(x, y))
        return;

    if(_map[x][y] == 1)
        color = false;
    else if(_map[x][y] == 2)
        color = true;
    else
    {
        return;
    }

    QVector <QPair <int, int> > group = findGroup(x, y, color);


    for(int i = 0 ; i < group.size() ; ++i)
    {
        int x = group.at(i).first, y = group.at(i).second;
        if(color)
            _map[x][y] = 9;
        else
            _map[x][y] = 8;
    }
    group = floodEmptyTerritory(color, x, y);

    bool deathSentence = true;
    for(int i = 0 ; i < group.size(); ++i)
    {
        deathSentence = isATerritory(color, group[i].first, group[i].second);

        if(!deathSentence)
        {
            break;
        }
    }

    if(!deathSentence)
    {

        unmark(group, 6);
        unmark(group, 7);
    }
    else
    {
        if(color)
            remark(group, 9, 5);
        else
            remark(group, 8, 4);
    }
}

void Goban::declareAlive(int x, int y)
{

}

bool Goban::checkKO(Goban & gobanNew, const Goban & gobanOld, bool color, int x, int y)
{
    return (gobanNew.playAMove(color, x, y) == 1 and gobanNew._map == gobanOld._map);
}
